package com.snowy.demo.log.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.snowy.demo.log.event.SysLogEvent;
import com.snowy.demo.log.po.SysLog;
import com.snowy.demo.log.util.SysLogUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.annotation.Order;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.time.LocalDateTime;

/**
 * @program: sno
 * @description: 操作日志切面业务
 * 1.捕获日志
 * 2.使用spring event异步入库
 * @author: cl.chen
 * @create: 2019-06-06 09:44
 **/
@Slf4j
@Aspect
@AllArgsConstructor
public class SysLogAspect {
    private final ApplicationEventPublisher publisher;

    /**
     * Service切面入口: 基于@SysLog
     **/
    @Around("@annotation(sysLog)")
    @Order(100)
    public Object aspectBySysLog(ProceedingJoinPoint point, com.snowy.demo.log.annotation.SysLog sysLog) throws Throwable {
        return saveSysLog(point, sysLog.value());
    }

    /**
     * Controller切面入口:
     * 1.基于@ApiOperation注解
     * 2.Get请求全部忽略
     **/
//    @Around("@annotation(apiOperation)")
//    @Order(90)
//    @SneakyThrows
//    public Object aspectByApiOperation(ProceedingJoinPoint point) {
//        //Get请求放行
//        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//        if (HTTPMethod.GET.name().equalsIgnoreCase(req.getMethod())) {
//            return point.proceed();
//        }
//        //其他的,记录日志
//        return saveSysLog(point, apiOperation.value());
//    }

    /**
     * 创建系统日志
     **/
    //@SneakyThrows
    private Object saveSysLog(ProceedingJoinPoint point, String title) throws Throwable {
        String strClassName = point.getTarget().getClass().getName();
        String strMethodName = point.getSignature().getName();
        log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);

        SysLog logVO = SysLogUtils.getSysLog();
        logVO.setTitle(title);
        Object[] args = point.getArgs();
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
                //ServletRequest不能序列化，从入参里排除，否则报异常：java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
                //ServletResponse不能序列化 从入参里排除，否则报异常：java.lang.IllegalStateException: getOutputStream() has already been called for this response
                continue;
            }
            arguments[i] = args[i];
        }
        String paramter = "";
        if (arguments != null) {
            try {
                paramter = JSONObject.toJSONString(arguments);
            } catch (Exception e) {
                paramter = arguments.toString();
            }
        }
        System.out.println("================:"+paramter.toString());
        logVO.setReqParams(paramter);
//        try {
//            logVO.setReqParams(JSON.toJSONString(point.getArgs()));
//        }catch (Exception e){
//            logVO.setReqParams("传递参数序列化异常");
//        }

        // 发送异步日志事件
        Long startTime = System.currentTimeMillis();
        logVO.setCreateDate(LocalDateTime.now());

        // <3>在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法
        // 如果忘记这样做就会导致通知被执行了,  但目标方法没有被执行
        Object obj =null;
        try {
             obj =point.proceed();
             logVO.setRespParams(JSON.toJSONString(obj));
        } catch (Throwable throwable) {
             Long endTime = System.currentTimeMillis();
             logVO.setTime(endTime - startTime);
             logVO.setException(throwable.toString());
             publisher.publishEvent(new SysLogEvent(logVO));
             throw throwable;
        }
        Long endTime = System.currentTimeMillis();
        logVO.setTime(endTime - startTime);
        publisher.publishEvent(new SysLogEvent(logVO));
        //<4>.环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
        return obj;
    }

}
